Skip to content

feat(cohort-1): warehouse-pull Celery tasks for catalog sources#3566

Open
blarghmatey wants to merge 5 commits into
mainfrom
worktree-cohort1-trino-tasks
Open

feat(cohort-1): warehouse-pull Celery tasks for catalog sources#3566
blarghmatey wants to merge 5 commits into
mainfrom
worktree-cohort1-trino-tasks

Conversation

@blarghmatey

Copy link
Copy Markdown
Member

What are the relevant tickets?

Part of https://github.com/mitodl/hq/issues/11510 (Cohort 1: DB-Backed Catalog Sources / Trino-pull)

Description (What does it do?)

Implements the Cohort 1 Trino-pull ETL pattern for MIT Learn's OL Data Platform migration: 7 new Celery tasks that pull pre-computed integrations__learn__* views from the warehouse and upsert them through the existing course/program loaders, replacing (once cut over) the equivalent API-based ETL pipelines for these sources.

New tasks (learning_resources/tasks.py): SyncMITxOnlineCoursesTask, SyncMITxOnlineProgramsTask, SyncXProCoursesTask, SyncXProProgramsTask, SyncMITEdXCoursesTask, SyncOCWCoursesTask, SyncMicromastersProgramsTask. Program tasks fetch_only their child courses, so are scheduled 15 minutes after their courses task.

New infrastructure (learning_resources/lib/warehouse.py):

  • BaseWarehouseETLTask — a Celery task base class that opens a warehouse connection, delegates to fetch_and_upsert, and handles connection lifecycle/error reporting (Sentry breadcrumbs).
  • iter_rows() — backend-agnostic row iteration built on the plain DB-API 2.0 cursor surface (execute/description/fetchmany). Trino is the only backend wired up today (settings.WAREHOUSE_BACKEND); a _connect_starrocks stub documents the intended migration path — since Trino, StarRocks, and DuckDB all expose the same cursor surface, adding a backend is a connector fill-in, not an ETL-layer rewrite.
  • Full-refresh vs. incremental sync: run() takes a full_refresh: bool = True kwarg. full_refresh=True (today's beat schedule default) pulls every row and prunes resources no longer present in the source — the self-healing baseline. full_refresh=False pulls only rows with last_modified greater than a persisted watermark (stored in Django's existing durable DB-backed cache, not a new model/migration) and skips pruning, since a partial pull must never be treated as the complete source state. Every integrations__learn__* view already exposes last_modified per the platform's marts contract, so this required no changes on the data platform side.

New transforms (learning_resources/etl/catalog_sources.py): pure dict -> dict functions mapping each view's flattened row contract (delimited strings for topics/instructors/runs, since the views intentionally avoid nested JSON) to the shape loaders.load_courses/load_programs expect.

Bug fix: _SAFE_IDENTIFIER's regex used $ instead of \Z to anchor the "safe view name" check. Python's $ matches just before a trailing newline (not strictly end-of-string), so a view_name ending in \n slipped past the guard. Fixed to \Z.

How can this be tested?

No live Trino endpoint is available yet in any environment (see https://github.com/mitodl/hq/issues/11509, "confirm MIT Learn deployment environment has network access to the Trino/Starburst endpoint" — still open), so there is no live-connectivity smoke test in this PR. All 116 tests in the touched files pass against mocked warehouse connections:

docker compose run --rm web uv run pytest \
  learning_resources/tasks_test.py \
  learning_resources/lib/warehouse_test.py \
  learning_resources/etl/catalog_sources_test.py
# 116 passed
  • warehouse_test.py covers connection dispatch (Trino/StarRocks/unknown backend), iter_rows batching/cursor-cleanup/SQL-injection-guard/since-filtering, and BaseWarehouseETLTask's full-refresh vs. incremental branching (watermark read/write, prune flag, error paths) — all against a hand-built DB-API-2.0-shaped mock, no Django app/DB required.
  • tasks_test.py covers each of the 7 tasks' wiring (view name -> transform -> loader -> clear_views_cache) plus explicit full-refresh vs. incremental coverage, against a real Django test DB.
  • catalog_sources_test.py covers the row-transform functions directly.

Reviewers can also sanity-check the beat schedule wiring in main/settings_celery.py and confirm the full_refresh=True kwargs match the intended daily-parallel-validation cadence described in the cohort's implementation guide.

Additional Context

Opening as a draft: this is Cohort 1 infrastructure landing ahead of the still-open network-access confirmation (https://github.com/mitodl/hq/issues/11509) and before parallel validation against the existing API-based ETL has started. Once Trino connectivity is confirmed from this deployment environment, the plan is to run both ETL paths in parallel for 2 weeks (≥99% record match) before cutover — tracked as a separate task in the epic.

Implements the Cohort 1 Trino-pull ETL pattern: 7 Sync*Task Celery tasks
(MITx Online courses/programs, xPRO courses/programs, MIT edX courses, OCW
courses, MicroMasters programs) that pull integrations__learn__* views from
the OL Data Platform warehouse and upsert through the existing
loaders.load_courses/load_programs pipeline.

- learning_resources/lib/warehouse.py: backend-agnostic warehouse-pull
  infrastructure (BaseWarehouseETLTask, iter_rows) built on the DB-API 2.0
  cursor surface so a future StarRocks/DuckDB backend is a drop-in
  connector, not an ETL-layer rewrite. Trino is the only wired-up backend
  today, selected via settings.WAREHOUSE_BACKEND.
- learning_resources/etl/catalog_sources.py: row transforms mapping each
  integrations__learn__* view's flattened contract to the course/program
  dict shape the existing loaders expect.
- Sync tasks support full_refresh (default, prunes stale resources) vs.
  incremental (since=<last watermark>, skips pruning) modes, selected via
  a full_refresh kwarg. Watermarks persist in the durable DB-backed cache
  so an incremental run resumes correctly across process restarts.
- Beat schedule entries registered in main/settings_celery.py, running
  full_refresh=True daily alongside the existing API-based ETL tasks
  during Cohort 1 parallel validation.

Also fixes a latent bug in the view-name safety check: the regex used `$`
instead of `\Z`, so a view_name ending in a trailing newline slipped past
the "unsafe identifier" guard (Python's `$` matches just before a trailing
newline, not strictly end-of-string).
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

OpenAPI Changes

No changes detected

View full changelog

Unexpected changes? Ensure your branch is up-to-date with main (consider rebasing).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a “warehouse-pull” ETL pattern for Cohort 1 catalog sources, adding new Celery tasks that query pre-computed integrations__learn__* views (Trino today) and upsert them via the existing course/program loaders. It also adds a small SQL-injection guard fix and supporting transforms/tests to enable parallel validation against the current API-based ETL pipelines.

Changes:

  • Added warehouse connectivity + row-iteration infrastructure (BaseWarehouseETLTask, connect_to_warehouse, iter_rows) with full-refresh vs incremental (watermark-based) support.
  • Added 7 new Celery tasks to sync catalog sources from warehouse views and scheduled them in Celery beat for daily full refresh.
  • Added catalog_sources row→loader-shape transforms and comprehensive unit tests for tasks, transforms, and warehouse helpers.

Reviewed changes

Copilot reviewed 10 out of 12 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
uv.lock Locks trino and its dependencies (lz4, etc.) for the new warehouse connector.
pyproject.toml Adds trino>=0.334.0 dependency for warehouse connectivity.
main/settings_course_etl.py Introduces warehouse backend + Trino connection settings.
main/settings_celery.py Adds daily Celery beat schedules for the new warehouse-sync tasks.
learning_resources/tasks.py Implements 7 new warehouse-pull ETL Celery tasks for Cohort 1 catalog sources.
learning_resources/tasks_test.py Adds integration-style tests validating task wiring, full vs incremental behavior, and connection cleanup.
learning_resources/lib/warehouse.py Adds backend-agnostic warehouse connection dispatch, safe view iteration, and the BaseWarehouseETLTask base class.
learning_resources/lib/warehouse_test.py Adds fast unit tests for connection dispatch, iter_rows, and watermark logic without loading the full Django app.
learning_resources/lib/init.py Ensures learning_resources.lib is a package for the new warehouse module.
learning_resources/etl/catalog_sources.py Adds dict→dict transforms mapping flattened warehouse rows into the loader input shape.
learning_resources/etl/catalog_sources_test.py Adds unit tests for transform helpers and per-source transforms.
env/backend.local.example.env Documents new warehouse-related environment variables for local setup.

Comment thread learning_resources/etl/catalog_sources.py Outdated
Comment thread learning_resources/lib/warehouse.py
Comment thread learning_resources/tasks.py Outdated
Comment thread main/settings_celery.py Outdated
Comment thread main/settings_celery.py Outdated
Comment thread learning_resources/etl/catalog_sources.py
- _connect_trino: fail fast with ImproperlyConfigured when TRINO_HOST/
  TRINO_USER are unset, instead of constructing BasicAuthentication(None,
  None) and surfacing a confusing low-level connect() failure. Auth is
  now optional (None) when TRINO_USER/TRINO_PASSWORD aren't both set,
  rather than always wrapping possibly-None credentials.
- tasks.py: stop hardcoding the "ol_warehouse_production" catalog into
  every Sync*Task's view_name. Confirmed via the installed trino client
  (trino.dbapi.Connection accepts catalog=... and threads it through
  ClientSession, so 2-part schema.table references resolve against the
  connection's default catalog) that view_name only needs to be
  schema-qualified — settings.TRINO_CATALOG already supplies the catalog
  on the connection. A staging/validation Trino cluster with a
  differently-named catalog is now honored via settings alone.
- settings_celery.py: gate the 7 warehouse-sync beat entries behind
  TRINO_HOST being configured (read directly, since settings_celery
  loads before settings_course_etl per main/settings.py's import order).
  No environment has Trino network access yet (mitodl/hq#11509), so
  these entries would otherwise be registered-but-guaranteed-to-fail,
  paging on connection errors daily. Fixed the accompanying schedule
  comment: hour=10 UTC is 6am EDT / 5am EST, not "6:00am EST" outright.
- catalog_sources.py: _parse_datetime now normalizes to UTC via
  parse(value).replace(tzinfo=UTC), matching the identical convention in
  learning_resources/etl/mitxonline.py and xpro.py — naive datetimes
  were triggering Django's naive-datetime warnings and were ambiguous
  under server-timezone assumptions. _split's default separator changed
  from ", " to "," (still .strip()'d per-part) to match the plain-comma
  convention used elsewhere in learning_resources/etl (ocw.py,
  podcast.py, sloan.py) — verified this is a no-op for the actual
  ol-data-platform views, which array_join on ", ", since strip()
  normalizes either separator to the same result, but plain "," is also
  correct if a view ever emits unspaced commas.

All 116 tests in the touched files still pass; ruff check/format clean
(3 remaining D103 findings in tasks.py are pre-existing, unrelated to
this PR). Verified via `manage.py check` and a direct settings import
that the beat-schedule gate actually produces 0 warehouse-sync entries
without TRINO_HOST set and 7 with it set.
@blarghmatey blarghmatey marked this pull request as ready for review July 5, 2026 18:49
Comment thread learning_resources/etl/catalog_sources.py
Sentry flagged that transform_micromasters_program omits "topics"
entirely, and loaders.load_program's `program_data.pop("topics", [])`
defaults to [] for a missing key — which load_topics treats as "clear
all topics", wiping any existing MicroMasters program topics on every
sync.

integrations__learn__micromasters_programs has no topics column (unlike
every other Cohort 1 view), so there's nothing to populate it with yet.
Set "topics": None instead, which is loaders.py's own existing sentinel
for "not provided, leave alone" (load_topics only acts when
`topics_data is not None`; the same None-vs-[] distinction already
governs `departments_data = program_data.pop("departments", None)` a few
lines below). This stops the data loss without inventing new loader
semantics.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Code review skipped — your organization's overage spend limit has been reached.

Code review is billed via overage credits. To resume reviews, an organization admin can raise the monthly limit at claude.ai/admin-settings/claude-code.

Once credits are available, push a new commit or reopen this pull request to trigger a review.

Comment thread learning_resources/lib/warehouse.py Outdated
@mbertrand mbertrand self-assigned this Jul 7, 2026
"Susskind": PlatformType.susskind.name,
"WHU": PlatformType.whu.name,
"xPRO": PlatformType.xpro.name,
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move to constants.py alongside other dicts?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e005b7d: moved XPRO_PLATFORM_TRANSFORM into learning_resources/etl/constants.py and import it from both xpro.py and catalog_sources.py instead of maintaining two copies of the same dict.

"start_date": _parse_datetime(start_on),
"end_date": _parse_datetime(end_on),
"instructors": instructors,
"prices": [],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

load_run unconditionally overwrites run.prices (loaders.py:327-328) and load_prices(run, []) clears resource_prices — omitting the key doesn't help. Since these tasks upsert the same rows as the API ETL (same etl_source + readable_id), every warehouse sync will wipe displayed prices for mitxonline/xpro/mit_edx runs until the next API run restores them — a daily, user-visible flap once the beat schedule goes live. Consider making parallel validation read-only against prod rows, or adding prices to the views first.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#3565 is aimed at handling this shift in responsibilities as we cut over different data flows.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed directly in e005b7d, independent of #3565 (that PR's pull-vs-push ownership guard can't actually arbitrate this: both the API-based ETL and this warehouse-pull task pass the identical etl_source value and are both "pull" from #3565's guard's point of view, so it can't tell them apart or protect one from the other — see the PR-level note on merge order).

The actual fix: _parse_runs now sets "prices": None instead of [], and loaders.load_run/load_prices (and load_instructors) now treat None as "not provided by this source, leave the existing value alone" — the same sentinel convention load_topics already uses for topics_data. An omitted key still defaults to [] (unchanged behavior for every existing caller, all of which always pass real data), so this is additive, not a behavior change for the current API-based pipelines.

"""
title = row["title"]
return {
"readable_id": row["readable_id"],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The view emits cast(program_id as varchar) ("3", "4"), but the API ETL synthesizes micromasters-program-{id} (micromasters.py:126) — prod currently has micromasters-program-3/4 (verified via the public API). Passing the bare id through creates duplicate programs, and the prune step then unpublishes the real ones; the next API run reverses it. Prefix here (and use the same prefixed value for run_id, which the API ETL also prefixes — micromasters.py:137), or fix the view in ol-data-platform.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e005b7d: transform_micromasters_program now prefixes readable_id/run_id with learning_resources.etl.micromasters.READABLE_ID_PREFIX ("micromasters-program-"), matching the API-based ETL. Added a regression test asserting the prefix is applied.

Comment thread learning_resources/lib/warehouse.py Outdated
conn.close()

if not full_refresh:
self._set_watermark(datetime.now(tz=UTC))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The watermark is stamped after the fetch completes, so rows modified between query execution and completion fall outside both this pull and the next incremental window (skipped until a full refresh heals them). Capture the timestamp before executing the query, or use the max last_modified seen. Not urgent while incremental is unscheduled, but worth fixing before that tier goes live.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e005b7d: the watermark is now captured immediately before fetch_and_upsert is called, not after it returns, so a row modified mid-fetch is still covered by the next incremental window. Added a freezegun-based regression test that advances the clock inside fetch_and_upsert and asserts the stored watermark is the pre-fetch time.

return [part.strip() for part in value.split(sep) if part.strip()]


def _parse_bool(value) -> bool:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_parse_bool(1) returns False. Trino returns real bools, but StarRocks (MySQL protocol) returns 1/0 — the migration this module explicitly plans for — which would silently unpublish everything. Suggest: if isinstance(value, (bool, int)): return bool(value).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e005b7d: _parse_bool now treats bool | int as native booleans (bool(value)), so StarRocks-style 1/0 BOOLEAN columns parse correctly instead of silently becoming False. Added (1, True)/(0, False) cases to the parametrized test.

"published": published,
"url": row.get("url"),
"semester": row.get("term"),
"year": row.get("year"),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

year lands in an IntegerField"2025" coerces fine, but an empty string would raise on save. Cheap to guard here, e.g. int(row["year"]) if row.get("year") else None.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e005b7d: year now coerces via int(row["year"]) if row.get("year") else None, so an empty string becomes None instead of raising on save. Added a regression test.

cur.close()


class BaseWarehouseETLTask(Task):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BaseWarehouseETLTask doesn't set acks_late=True. The other long-running ETL tasks in this module do (e.g. get_ocw_data, ingest_edx_run_archive, import_*_files). A worker lost mid-pull loses the work and the message is already acked. Low-impact today (daily full_refresh=True is idempotent/self-healing, and a failed incremental leaves the watermark untouched so the next run re-covers the gap), but acks_late=True on the base would match the rest of the ETL task fleet. Optional.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e005b7d: added acks_late = True to BaseWarehouseETLTask, matching the rest of the ETL task fleet.

# ---------------------------------------------------------------------------


def transform_micromasters_program(row: dict) -> dict:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Distinct from the prices-wipe and readable_id issues already flagged: this transform also omits certification, certification_type, start_date/end_date, availability, and pace, all of which the API ETL sets (micromasters.py:133-149). Those are non-destructive — loaders only write keys present in the dict — but post-cutover they'd freeze at their last API-written values. Worth recording in the cutover plan: extend the view with these columns, or explicitly keep the API path as owner of them.

instructors is the exception, and it's destructive: _program_run provides no instructors key, so load_run pops the default [] and load_instructors(run, []) deletes every RunInstructorRelationship for the run (loaders.py:224). Program-run instructors will be wiped on every warehouse sync — same class of issue as prices, and this applies to the mitxonline/xpro program runs too.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The instructors-wipe half is fixed in e005b7d: _program_run now explicitly sets "instructors": None (and "prices": None), which loaders.load_run's new None-sentinel treats as "leave alone" rather than clearing — this now applies to mitxonline/xpro/micromasters program runs alike, as you flagged.

The certification/certification_type/dates/availability/pace gap is left as-is per your note — those keys are simply omitted from the transform dicts (not set to []/falsy), and upsert_course_or_program/load_program only write keys present in the dict, so they freeze at whatever the API-based ETL last set rather than getting clobbered. Non-destructive, as you said; recording it here as the known gap to close (extend the views, or keep the API path authoritative for these fields) before cutover.

…fety

- load_run/load_instructors/load_prices: support None (vs an omitted
  key, which still defaults to []) as the "not provided by this
  source, leave existing value alone" sentinel for instructors/prices,
  the same convention load_topics already uses for topics. The
  warehouse views have no pricing data and no program-run instructor
  data, so without this every warehouse sync silently wiped prices and
  program-run instructors the API-based ETL had already set.
- catalog_sources.transform_micromasters_program: prefix readable_id
  and run_id with micromasters.READABLE_ID_PREFIX, matching the
  API-based ETL. The view emits the bare program_id; passing it
  through unprefixed created a duplicate program and the prune step
  then unpublished the real one.
- Move XPRO_PLATFORM_TRANSFORM into etl/constants.py and import it
  from both xpro.py and catalog_sources.py instead of maintaining two
  copies of the same dict.
- _parse_bool: treat ints as bools (StarRocks/MySQL protocol returns
  1/0 for BOOLEAN columns; this codebase's migration plan explicitly
  covers that backend).
- transform_ocw_course: guard the year field against an empty string,
  which would raise on save against the IntegerField.
- warehouse.iter_rows: read cursor.description from the first fetched
  batch rather than immediately after execute(), since not every
  DB-API driver populates it before the first fetch (PEP 249 leaves
  this driver-defined).
- BaseWarehouseETLTask: set acks_late=True, matching the rest of the
  ETL task fleet, and capture the incremental watermark before the
  fetch starts (not after), so a row modified mid-fetch is still
  covered by the next incremental window.

All touched suites (catalog_sources, warehouse, loaders, xpro, tasks,
pipelines) pass: 488 passed.
@blarghmatey

Copy link
Copy Markdown
Member Author

Pushed e005b7d5d addressing the outstanding review feedback (individual replies on each thread above):

  • Prices/instructors wipe during parallel validation (@mbertrand): fixed by making loaders.load_run/load_instructors/load_prices treat None as "not provided, leave existing value alone" — the same sentinel load_topics already uses for topics_data. The warehouse-pull transforms now pass None for prices (course & program runs) and instructors (program runs), instead of [].
  • MicroMasters readable_id/run_id mismatch (duplicate programs + unpublish flapping): fixed — now prefixed with micromasters-program-, matching the API-based ETL.
  • XPRO_PLATFORM_TRANSFORM de-duplicated into etl/constants.py.
  • _parse_bool int handling, OCW year empty-string guard, acks_late=True, watermark-before-fetch, and the cur.description timing all fixed — see individual thread replies for detail.
  • All touched suites pass locally (catalog_sources, warehouse, loaders, xpro, tasks, pipelines): 488 passed.

On merging #3565 into this PR: I don't think it should be merged in, and I don't think it actually covers the parallel-validation risk that prompted the "aimed at handling this shift in responsibilities" comment on the prices thread.

#3565's ETLSourceOwnership guard is a binary pull/push switch keyed on (etl_source, resource_type). It's built for the case where exactly one pipeline is authoritative at a time and you flip a flag at cutover (e.g. SEE/Sloan moving from a Celery pull to a Dagster push asset). It can't arbitrate between two simultaneous pull writers of the same etl_source — which is exactly this PR's situation during the 2-week parallel-validation window: the existing API-based ETL and these new warehouse-pull tasks both pass e.g. ETLSource.mitxonline.name into load_courses/load_programs, and #3565's guard sees them as indistinguishable ("pull" either way). Setting that pair to push would block both callers, not just one.

So the two are complementary but solve different problems, and neither blocks the other at the code level (verified: no line-level overlap — #3565 touches the top of load_courses/load_programs and sync_canvas_courses; this PR's fixes touch load_run/load_instructors/load_prices). #3565 looks close to mergeable on its own merits (the CHANGES_REQUESTED-adjacent bot findings there are already resolved) and is worth landing independently for the sources it's actually meant to protect. It just isn't the fix for this PR's validation-window risk — the prices/instructors/readable_id fixes above are.

@mbertrand mbertrand left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great, just a couple more questions.

Comment thread main/settings_celery.py
},
}
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If TRINO_HOST is set, maybe these old ETL scheduled tasks should not be enabled?

        "update_edx-courses-every-1-days": {
            "task": "learning_resources.tasks.get_mit_edx_data",
            "schedule": crontab(minute=0, hour=5),
        },
        "update-micromasters-programs-every-1-days": {
            "task": "learning_resources.tasks.get_micromasters_data",
            "schedule": crontab(minute=0, hour=5),  # 1:00am EST
        },
        "update-mitxonline-courses-every-6-hours": {
            "task": "learning_resources.tasks.get_mitxonline_data",
            "schedule": crontab(minute=0, hour="*/6"),
        },
        "update-xpro-courses-every-1-days": {
            "task": "learning_resources.tasks.get_xpro_data",
            "schedule": crontab(minute=0, hour=5),  # 1:00am EST
        },

@mbertrand mbertrand Jul 8, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another question - @pdpinch was wondering if it might be possible to send webhooks from data-platform instead whenever there is a new or updated course/program that should be processed from these sources. And is data-platform getting this data from the source API's (like mitlearn currently does) or from the source databases?

Also, the manner in which mitxonline courses are ingested is changing right now so that B2B/invariant course runs are ingested (currently they are not) - #3543

Comment thread main/settings_celery.py
Comment on lines +255 to +259
"warehouse-sync-ocw-courses-every-1-days": {
"task": "learning_resources.tasks.SyncOCWCoursesTask",
"schedule": crontab(minute=15, hour=11),
"kwargs": {"full_refresh": True},
},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is currently handled via webhooks - is this meant to replace those webhooks or complement them (ie fill any missing gaps caused by webhooks that failed to be delivered or processed for whatever reason)?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intent is to replace the webhooks, though it does create a tradeoff of frequency/timeliness. This is largely an opportunistic body of work so there may be some additional design work necessary before it all lands. If timeliness is a core requirement for OCW data then we can remove this source from the current PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants